﻿using System;
using System.IO;
using System.Net;

namespace Safebrowsing
{
    /// <summary>
    /// This class is used for making HTTP requests to the safebrowsing api
    /// and to parse it's response.
    /// </summary>
    public class SafebrowsingApiClient
    {
        private readonly SafebrowsingApiServicePoint m_ApiServicePoint;

        /// <summary>
        /// Creates an instance of the SafebrowsingApiClient object.
        /// </summary>
        public SafebrowsingApiClient(SafebrowsingApiServicePoint apiServicePoint)
        {
            Logger.Verbose("Initializing SafebrowsingApiClient. Service point is {0}.", apiServicePoint);
            m_ApiServicePoint = apiServicePoint;
            Logger.Verbose("SafebrowsingApiClient has been initialized successfully.");
        }

        /// <summary>
        /// Gets or sets api client proxy
        /// </summary>
        public IWebProxy Proxy { get; set; }

        /// <summary>
        /// Request for list data
        /// </summary>
        /// <param name="dataRequest"></param>
        /// <returns></returns>
        /// <exception cref="SafebrowsingApiException">Thrown when we cannot access safebrowsing API or it has returned error code.</exception>
        public DataResponse RequestData(DataRequest dataRequest)
        {
            try
            {
                byte[] data = Post(m_ApiServicePoint.DataRequestUri, dataRequest.ToString());
                if (data == null || data.Length == 0) return null;
                return new DataResponse(data);
            }
            catch (WebException ex)
            {
                Logger.Warn("Error thrown while requesting data. Data request is {0}. Exception is {1}.", dataRequest,
                            ex);
                throw new SafebrowsingApiException("Error while requesting data", ex, GetErrorCode(ex));
            }
            catch (Exception ex)
            {
                throw new SafebrowsingApiException(
                    string.Format("Unexpected exception while requesting data. Data request is: {0}", dataRequest), ex);
            }
        }

        /// <summary>
        /// Downloads and parses redirect data
        /// </summary>
        /// <param name="redirectUrl"></param>
        /// <returns></returns>
        public RedirectResponse RequestRedirectData(Uri redirectUrl)
        {
            try
            {
                byte[] data = Post(redirectUrl, string.Empty);
                if (data == null) return null;
                return new RedirectResponse(data);
            }
            catch (WebException ex)
            {
                Logger.Warn("Error thrown while requesting chunks data from redirect url {0}. Exception is {1}.",
                            redirectUrl, ex);
                throw new SafebrowsingApiException("Error while requesting data", ex, GetErrorCode(ex));
            }
            catch (Exception ex)
            {
                Logger.Warn("Error thrown while requesting chunks data from redirect url {0}. Exception is {1}.",
                            redirectUrl, ex);
                throw new SafebrowsingApiException(
                    string.Format("Unexpected exception while requesting chunks data from redirect url: {0}",
                                  redirectUrl), ex);
            }
        }

        /// <summary>
        /// Request for full-length hashes
        /// </summary>
        /// <param name="hashRequest"></param>
        /// <returns></returns>
        public HashResponse RequestHash(HashRequest hashRequest)
        {
            try
            {
                byte[] data = Post(m_ApiServicePoint.HashRequestUri, hashRequest.ToBytes());
                if (data == null) return null;
                return new HashResponse(data);
            }
            catch (WebException ex)
            {
                Logger.Warn("Error thrown while requesting hashes. Hash request: {0}. Exception: {1}.", hashRequest, ex);
                throw new SafebrowsingApiException("Error while requesting hashes", ex, GetErrorCode(ex));
            }
            catch (Exception ex)
            {
                Logger.Warn("Error while requesting full hashes. Hash request: {0}. Exception: {1}", hashRequest, ex);
                throw new SafebrowsingApiException("Unexpected exception while requesting full hashes", ex);
            }
        }

        private byte[] Post(Uri uri, string postdata)
        {
            Logger.Verbose("POST {0}. Post data: {1}", uri, postdata);
            return Post(uri, Utils.DefaultEncoding.GetBytes(postdata));
        }

        private byte[] Post(Uri uri, byte[] postdata)
        {
            var webRequest = (HttpWebRequest) WebRequest.Create(uri);
            webRequest.Method = "POST";
            webRequest.ContentLength = postdata.Length;
            webRequest.UseDefaultCredentials = true;
            webRequest.Proxy = Proxy;
            webRequest.SendChunked = false;
            webRequest.AllowAutoRedirect = false;
            using (Stream requestStream = webRequest.GetRequestStream())
            {
                requestStream.Write(postdata, 0, postdata.Length);
                requestStream.Flush();
                using (var response = (HttpWebResponse) webRequest.GetResponse())
                {
                    if (response.StatusCode == HttpStatusCode.OK)
                    {
                        using (Stream responseStream = response.GetResponseStream())
                        {
                            return Utils.ReadToEnd(responseStream);
                        }
                    }

                    return null;
                }
            }
        }

        /// <summary>
        /// Gets corresponding safebrowsing error code value from the WebException.
        /// Looking for HTTP response status code. If not found -- returning Uknown error code.
        /// </summary>
        /// <param name="exception"></param>
        /// <returns></returns>
        private static SafebrowsingErrorCode GetErrorCode(WebException exception)
        {
            if (exception != null && exception.Response != null && exception.Response is HttpWebResponse)
            {
                var webResponse = (HttpWebResponse) exception.Response;
                switch (webResponse.StatusCode)
                {
                    case HttpStatusCode.BadRequest:
                        return SafebrowsingErrorCode.BadRequest;
                    case HttpStatusCode.Unauthorized:
                        return SafebrowsingErrorCode.NotAuthorized;
                    case HttpStatusCode.Forbidden:
                        return SafebrowsingErrorCode.Forbidden;
                    case HttpStatusCode.ServiceUnavailable:
                        return SafebrowsingErrorCode.ServiceUnavailable;
                    case HttpStatusCode.HttpVersionNotSupported:
                        return SafebrowsingErrorCode.HttpVersionNotSupported;
                }
            }

            return SafebrowsingErrorCode.Unknown;
        }
    }
}